/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.core;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.annotation.CustomizeIdValue;
import cn.star.framework.annotation.IdValueConvert;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Id;
import lombok.Getter;
import lombok.Setter;
import org.fusesource.jansi.Ansi.Color;
import org.hibernate.MappingException;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 主键生成策略<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
public interface IdStrategyValue extends IdentifierGenerator {

  /**
   * 默认实现
   *
   * @param type {@link Type}
   * @param properties {@link Properties}
   * @param registry {@link ServiceRegistry}
   * @throws MappingException
   */
  @Override
  default void configure(Type type, Properties properties, ServiceRegistry registry)
      throws MappingException {
    this.setIdStrategyValueConfigure(type, properties, registry);
  }

  /**
   * {@link #configure(Type, Properties, ServiceRegistry)}
   *
   * @param type {@link Type}
   * @param properties {@link Properties}
   * @param registry {@link ServiceRegistry}
   * @throws MappingException
   */
  default void setIdStrategyValueConfigure(
      Type type, Properties properties, ServiceRegistry registry) {
    Object entityName = properties.get(IdentifierGenerator.ENTITY_NAME);
    Object idStrategyValueName = properties.get(IdentifierGenerator.GENERATOR_NAME);
    Object pkColumnName = properties.get(PersistentIdentifierGenerator.PK);

    IdStrategyValueConfigure configure =
        new IdStrategyValueConfigure(entityName, idStrategyValueName, pkColumnName);

    configure.setType(type);
    configure.setProperties(properties);
    configure.setRegistry(registry);

    this.setIdStrategyValueConfigure(configure);
  }

  /**
   * 设置配置信息
   *
   * @param configure {@link IdStrategyValueConfigure}
   */
  void setIdStrategyValueConfigure(IdStrategyValueConfigure configure);

  /**
   * 获取配置信息
   *
   * @return
   */
  IdStrategyValueConfigure getIdStrategyValueConfigure();

  /**
   * 获取值
   *
   * @param entity {@link IEntity}
   * @param defaultValue
   * @return
   */
  default Serializable getValue(Object entity, Serializable defaultValue) {
    IdStrategyValueConfigure configure = this.getIdStrategyValueConfigure();
    if (configure == null) {
      this.debugger(
          Constant.getAnsi(Color.RED)
              .a("Not found: " + IdStrategyValueConfigure.class.getName())
              .reset()
              .toString());
    }
    List<IdAnnotationColumnConfigure> idAnnotationColumnConfigures = new ArrayList<>();
    try {
      Field[] fields = ReflectUtil.getFields(entity.getClass());
      for (Field field : fields) {
        Id id = field.getAnnotation(Id.class);
        if (id != null) {
          Column column = field.getAnnotation(Column.class);
          Set<String> possibleColumnNames = new HashSet<>();
          possibleColumnNames.add(field.getName());
          possibleColumnNames.add(
              StrUtil.toUnderlineCase(field.getName()).toLowerCase(Locale.ROOT));
          if (StrUtil.isNotBlank(column.name())) {
            possibleColumnNames.add(column.name());
          }
          // 判断当前字段是否是需要设置值的字段，防止不是当前字段的字段将 defaultValue 或者将 原始值 设置到字段上
          // 不使用 isCurrentSetValueOfColumn 来判断也不会出现问题，因为此处不会改变实体字段的值，也不能改变实体字段的值
          boolean isCurrentSetValueOfColumn = false;
          if (configure != null) {
            for (String possibleColumnName : possibleColumnNames) {
              if (possibleColumnName.equalsIgnoreCase(configure.getPkColumnName())) {
                isCurrentSetValueOfColumn = true;
                break;
              }
            }
          }
          IdValueConvert convert = field.getAnnotation(IdValueConvert.class);
          CustomizeIdValue customizeIdValue = field.getAnnotation(CustomizeIdValue.class);
          Object v = ReflectUtil.getFieldValue(entity, field);
          IdAnnotationColumnConfigure idAnnotationColumnConfigure =
              new IdAnnotationColumnConfigure(
                  field,
                  // 若使用了自定义主键值的注解却没有设置主键的值，则使用生成的主键的值
                  isCurrentSetValueOfColumn
                      ? (v == null ? defaultValue : (Serializable) v)
                      : (v == null ? null : (Serializable) v),
                  entity,
                  customizeIdValue != null);
          idAnnotationColumnConfigure.setUse(isCurrentSetValueOfColumn);
          idAnnotationColumnConfigure.setConvertClass(convert == null ? null : convert.value());
          idAnnotationColumnConfigures.add(idAnnotationColumnConfigure);
        }
      }
    } catch (Exception e) {
      LoggerFactory.getLogger(this.getClass()).error(e.getMessage());
    }

    if (idAnnotationColumnConfigures.size() > 1) {
      this.debugger(
          "Use union primary key. Number of primary keys: {}", idAnnotationColumnConfigures.size());
    }

    IdAnnotationColumnConfigure column =
        idAnnotationColumnConfigures.stream()
            .filter(IdAnnotationColumnConfigure::isUse)
            .findFirst()
            .orElse(null);
    if (column == null) {
      column = new IdAnnotationColumnConfigure(null, defaultValue, entity, false);
      this.debugger(
          Constant.getAnsi(Color.RED)
              .a(
                  "Not found: "
                      + IdAnnotationColumnConfigure.class.getName()
                      + ", Use the default new instance")
              .reset()
              .toString());
    }

    // 出现当字段主键值设置为 "" 时，则会出问题，需要避免这种问题；当设置为 "" 则认为是空
    Serializable value =
        column.isUseCustomizeIdValue() && !StrUtil.isBlankIfStr(column.getValue())
            ? column.getValue()
            : defaultValue;

    value = this.getIdStrategyGeneratorValueConvertFunction().invoke(column, value, configure);

    return value;
  }

  /**
   * 获取值转换函数{@link IdStrategyGeneratorValueConvertFunction}
   *
   * @return 函数实例
   */
  default IdStrategyGeneratorValueConvertFunction getIdStrategyGeneratorValueConvertFunction() {
    return (column, value, configure) -> {
      IdStrategyGeneratorValueConvertFunction function =
          column.getIdStrategyGeneratorValueConvertFunction();
      if (function != null) {
        return function.invoke(column, value, configure);
      }

      return this.valueTypeConvert(column.getField(), value);
    };
  }

  /**
   * 根据主键的类型来转换值
   *
   * @param field 需要转换的目标类型 {@link Field}
   * @param value 当前带转化类型的值<br>
   *     <ul>
   *       <li>使用{@link IdStrategyValue}生成的原始数据
   *       <li>使用{@link CustomizeIdValue}注释而自定义设置的值
   *     </ul>
   *
   * @return 值
   */
  default Serializable valueTypeConvert(Field field, Serializable value) {
    if (field == null) {
      return value;
    }

    try {
      Class<?> type = field.getType();
      if (type.isInstance(value)) {
        return value;
      }
      String fieldType = type.getName();
      this.debugger(
          "Convert -> \noriginal value Type: "
              + value.getClass().getName()
              + ", current field type："
              + fieldType
              + ", convert value: "
              + value);

      // current support convert types.
      List<String> strTypes = Collections.singletonList(String.class.getName());
      List<String> longTypes = CollectionUtil.toList(Long.class.getName(), long.class.getName());
      List<String> intTypes = CollectionUtil.toList(Integer.class.getName(), int.class.getName());
      List<String> shortTypes = CollectionUtil.toList(Short.class.getName(), short.class.getName());

      if (strTypes.contains(fieldType)) {
        value = String.valueOf(value.toString());
      } else if (longTypes.contains(fieldType)) {
        value = Long.valueOf(value.toString());
      } else if (intTypes.contains(fieldType)) {
        value = Integer.valueOf(value.toString());
      } else if (shortTypes.contains(fieldType)) {
        value = Short.valueOf(value.toString());
      } else {
        throw new Exception("Not support type: " + fieldType);
      }

      return value;
    } catch (Exception e) {
      this.debugger(
          Constant.getAnsi(Color.RED).a("Convert Error: " + e.getMessage()).reset().toString());
      throw new RuntimeException(e);
    }
  }

  /**
   * 输出Debugger日志
   *
   * @param template 日志模板
   * @param arguments 参数列表
   */
  default void debugger(String template, Object... arguments) {
    Logger logger = LoggerFactory.getLogger(this.getClass());
    if (logger != null && logger.isDebugEnabled()) {
      if (ArrayUtil.isNotEmpty(arguments)) {
        for (int i = 0; i < arguments.length; i++) {
          Object v = arguments[i];
          if (v != null) {
            arguments[i] = Constant.getAnsi(Color.GREEN).bold().a(v).reset().toString();
          }
        }
        template =
            Constant.getAnsi(Color.MAGENTA)
                .a(template)
                .reset()
                .toString()
                .replaceAll("\\{}", "\u001B[m{}\u001B[35m");
      }

      logger.debug(StrUtil.format(template, arguments));
    }
  }

  /** 持久化实体的主键生成策略配置信息 */
  @Getter
  class IdStrategyValueConfigure {
    /** 实体类名 {@link IEntity} .class.getName() */
    private final String entityName;

    /** 主键生成策略 {@link IdStrategyValue} .class.getName() */
    private final String idStrategyValueName;

    /** 主键字段名称 {@link Column#name()} */
    private final String pkColumnName;

    @Setter private Type type;
    @Setter private Properties properties;
    @Setter private ServiceRegistry registry;

    public IdStrategyValueConfigure(
        Object entityName, Object idStrategyValueName, Object pkColumnName) {
      this.entityName = entityName == null ? "" : entityName.toString();
      this.idStrategyValueName = idStrategyValueName == null ? "" : idStrategyValueName.toString();
      this.pkColumnName = pkColumnName == null ? "" : pkColumnName.toString();
    }
  }

  /** 使用{@link Id}注解的字段的信息 */
  @Getter
  class IdAnnotationColumnConfigure {
    /** 主键字段 */
    @Setter private Field field;

    /** 主键字段原始数据，若没有设置原始数据，则将当前生成的主键值进行赋值 */
    @Setter private Serializable value;

    /** 当前实体 */
    @Setter private Object entity;

    /** 是否使用{@link CustomizeIdValue}注解而使用值覆盖 */
    @Setter private boolean useCustomizeIdValue;

    private final Set<String> pkColumnNames;

    /** 是否是在当前主键生成策略中需要设置值的字段 */
    @Setter boolean use = false;

    /** {@link IdStrategyGeneratorValueConvertFunction} */
    @Setter Class<? extends IdStrategyGeneratorValueConvertFunction> convertClass;

    private static final Map<
            Class<? extends IdStrategyGeneratorValueConvertFunction>,
            IdStrategyGeneratorValueConvertFunction>
        CONVERT_MAP = new HashMap<>();

    public IdAnnotationColumnConfigure(
        Field field, Serializable value, Object entity, boolean useCustomizeIdValue) {
      this.field = field;
      this.value = value;
      this.entity = entity;
      this.useCustomizeIdValue = useCustomizeIdValue;
      this.pkColumnNames = new HashSet<>();

      this.setPkColumnNames(field);
    }

    public IdStrategyGeneratorValueConvertFunction getIdStrategyGeneratorValueConvertFunction() {
      try {
        if (convertClass != null) {
          IdStrategyGeneratorValueConvertFunction convert = CONVERT_MAP.get(convertClass);
          if (convert == null) {
            convert = convertClass.newInstance();
            CONVERT_MAP.put(convertClass, convert);
          }

          return convert;
        }
      } catch (Exception e) {
        Logger logger = LoggerFactory.getLogger(this.getClass());
        logger.error(e.getMessage());
      }

      return null;
    }

    private void setPkColumnNames(Field field) {
      if (field == null) {
        // 防止出现空指针异常，可能使用 @Inheritance 注解的多表情况
        return;
      }
      Column column = field.getAnnotation(Column.class);
      if (column != null) {
        pkColumnNames.add(column.name());
      } else {
        pkColumnNames.add(field.getName());
        pkColumnNames.add(StrUtil.toUnderlineCase(field.getName()));
        pkColumnNames.add(StrUtil.toUnderlineCase(field.getName()).toLowerCase(Locale.ROOT));
        pkColumnNames.add(StrUtil.toUnderlineCase(field.getName()).toUpperCase(Locale.ROOT));
      }
    }
  }

  /** 转换函数 */
  @FunctionalInterface
  interface IdStrategyGeneratorValueConvertFunction {

    /**
     * {@link IdStrategyValue}生成的值的转换函数
     *
     * @param column {@link IdAnnotationColumnConfigure}
     * @param value 值
     * @param configure {@link IdStrategyValueConfigure}
     * @return 值
     */
    Serializable invoke(
        IdAnnotationColumnConfigure column, Serializable value, IdStrategyValueConfigure configure);
  }
}
